package mk.finki.chemistry.dice;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.text.SimpleDateFormat;
import java.util.Date;

import mk.finki.chemistry.dice.grid.ClientEvolveStrategy;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.apache.log4j.Logger;
import org.homedns.dade.jcgrid.client.GridClient;
import org.homedns.dade.jcgrid.client.GridNodeClientConfig;
import org.homedns.dade.jcgrid.cmd.MainCmd;
import org.homedns.dade.jcgrid.message.GridMessageWorkRequest;
import org.homedns.dade.jcgrid.message.GridMessageWorkResult;
import org.jgap.Configuration;
import org.jgap.FitnessFunction;
import org.jgap.Gene;
import org.jgap.IChromosome;
import org.jgap.distr.grid.DefaultClientFeedback;
import org.jgap.distr.grid.IClientEvolveStrategy;
import org.jgap.distr.grid.IClientFeedback;
import org.jgap.distr.grid.IGridConfiguration;
import org.jgap.distr.grid.IRequestSplitStrategy;
import org.jgap.distr.grid.JGAPClient;
import org.jgap.distr.grid.JGAPRequest;
import org.jgap.distr.grid.JGAPResult;

public class DiceClient extends Thread {
	/** String containing the CVS revision. Read out via reflection! */
	private final static String CVS_REVISION = "$Revision: 1.14 $";

	private final static String className = JGAPClient.class.getName();

	private static Logger log = Logger.getLogger(className);

	protected GridNodeClientConfig m_gridconfig;

	protected JGAPRequest m_workReq;

	private GridClient m_gc;

	private IGridConfiguration m_gridConfig;

	public DiceClient(GridNodeClientConfig a_gridconfig, String a_clientClassName) throws Exception {
		m_gridconfig = a_gridconfig;
		Class client = Class.forName(a_clientClassName);
		m_gridConfig = (IGridConfiguration) client.getConstructor(new Class[] {}).newInstance(
				new Object[] {});
		m_gridConfig.initialize(m_gridconfig);
		// Setup work request.
		// -------------------
		JGAPRequest req = new JGAPRequest(m_gridconfig.getSessionName(), 0,
				m_gridConfig.getConfiguration());
		req.setWorkerReturnStrategy(m_gridConfig.getWorkerReturnStrategy());
		req.setGenotypeInitializer(m_gridConfig.getGenotypeInitializer());
		req.setEvolveStrategy(m_gridConfig.getWorkerEvolveStrategy());
		// Evolution takes place on client here!
		// -------------------------------------
		req.setEvolveStrategy(null);
		setWorkRequest(req);
		// Start the threaded process.
		// ---------------------------
		start();
		join();
	}

	public void setWorkRequest(JGAPRequest a_request) {
		m_workReq = a_request;
	}

	protected GridClient startClient() throws Exception {
		GridClient gc = new GridClient();
		gc.setNodeConfig(m_gridconfig);
		gc.start();
		return gc;
	}

	/**
	 * Threaded: Splits work, sends it to workers and receives computed solutions.
	 * 
	 * @author Klaus Meffert
	 * @since 3.01
	 */
	public void run() {
		try {
			if (m_gridConfig.getClientFeedback() == null) {
				// Use dummy feedback handler.
				// ---------------------------
				log.info("No feedback handler set, using DefaultClientFeedback");
				m_gridConfig.setClientFeedback(new DefaultClientFeedback());
			}
			// Start client.
			// -------------
			m_gc = startClient();
			try {
				// Initialize evolution.
				// ---------------------
				IClientEvolveStrategy clientEvolver = m_gridConfig.getClientEvolveStrategy();
				if (clientEvolver != null) {
					clientEvolver.initialize(m_gc, getConfiguration(), m_gridConfig.getClientFeedback());
				}
				// Do the evolution.
				// -----------------
				evolve(m_gc);
			} finally {
				try {
					m_gc.stop();
				} catch (Exception ex) {
				}
			}
		} catch (Exception ex) {
			ex.printStackTrace();
			if (m_gridConfig.getClientFeedback() != null) {
				m_gridConfig.getClientFeedback().error("Error while doing the work", ex);
			}
		}
	}

	protected void sendWorkRequests(JGAPRequest[] a_workList) throws Exception {
		// Send work requests.
		// -------------------
		for (int i = 0; i < a_workList.length; i++) {
			JGAPRequest req = a_workList[i];
			m_gridConfig.getClientFeedback().sendingFragmentRequest(req);
			m_gc.send(new GridMessageWorkRequest(req));
			if (this.isInterrupted()) {
				break;
			}
		}
	}

	protected void receiveWorkResults(JGAPRequest[] workList) throws Exception {
		IClientFeedback feedback = m_gridConfig.getClientFeedback();
		// Receive work results.
		// ---------------------
		int idx = -1;
		for (int i = 0; i < workList.length; i++) {
			feedback.setProgressValue(i + workList.length);
			m_gc.getGridMessageChannel();
			GridMessageWorkResult gmwr = (GridMessageWorkResult) m_gc.recv(i);
			JGAPResult workResult = (JGAPResult) gmwr.getWorkResult();
			m_gridConfig.getClientEvolveStrategy().resultReceived(workResult);
			idx = workResult.getRID();
			// Fire listener.
			// --------------
			feedback.receivedFragmentResult(workList[idx], workResult, idx);
			if (this.isInterrupted()) {
				break;
			}
		}
	}

	/**
	 * If necessary: override to implement your evolution cycle individually.
	 * 
	 * @param gc
	 *          GridClient
	 * @throws Exception
	 */
	protected void evolve(GridClient gc) throws Exception {
		// Do the complete evolution cycle until end.
		// ------------------------------------------
		IClientFeedback feedback = m_gridConfig.getClientFeedback();
		feedback.beginWork();
		IClientEvolveStrategy evolver = m_gridConfig.getClientEvolveStrategy();
		IRequestSplitStrategy splitter = m_gridConfig.getRequestSplitStrategy();
		int evolutionIndex = 0;

		final String fileName = "run_" + new SimpleDateFormat("dd:MM:yyyy_hh:mm:ss").format(new Date());

		BufferedWriter out = new BufferedWriter(new FileWriter(fileName));
		out.write("Printing best chromosomes per generation:\n");
		out.close();

		int generation = 1;
		do {
			boolean end = false;
			boolean evolutionOK = false;
			do {

				JGAPRequest[] workRequests = evolver.generateWorkRequests(m_workReq, splitter, null);
				feedback.setProgressMaximum(0);
				feedback.setProgressMaximum(workRequests.length - 1);
				sendWorkRequests(workRequests);
				if (this.isInterrupted()) {
					break;
				}
				evolver.afterWorkRequestsSent();
				receiveWorkResults(workRequests);

				if (!evolutionOK)
					evolutionOK = ((ClientEvolveStrategy) evolver).evolve1();
				end = evolutionOK;

				System.out.println("Evolution OK?:" + (evolutionOK ? "yes" : "no"));

				int nonEvalChromosomeCount = 0;
				for (IChromosome c : m_workReq.getPopulation().getChromosomes())
					if (c.getFitnessValueDirectly() == FitnessFunction.NO_FITNESS_VALUE)
						nonEvalChromosomeCount++;

				if (nonEvalChromosomeCount > 0)
					end = false;

				System.out.println("Found non evaluated chromosome?:"
						+ (end ? "no" : "yes " + nonEvalChromosomeCount));

			} while (!end);

			Chromosome c = (Chromosome) m_workReq.getPopulation().determineFittestChromosome();

			Gene[] genes = c.getGenes();

			final double chargeC = (Double) genes[0].getAllele();
			final double epsilonC = (Double) genes[1].getAllele();
			final double sigmaC = (Double) genes[2].getAllele();
			final double epsilonCl = (Double) genes[3].getAllele();
			final double sigmaCl = (Double) genes[4].getAllele();

			out = new BufferedWriter(new FileWriter(fileName, true));
			out.write("[" + new SimpleDateFormat("dd:MM:yyyy hh:mm:ss").format(new Date()) + "] ");
			out.write(generation++ + ": ");
			out.write(" fitness:" + c.getFitnessValue());

			out.write(" Density:" + c.getDensity());
			out.write(" ThermalExpansion:" + c.getThermalExpansion());
			out.write(" MolarSpecificHeat:" + c.getMolarSpecificHeat());
			out.write(" IsothermalCompressibility:" + c.getIsothermalCompressibility());

			out.write(" charge_C:" + chargeC);
			out.write(" epsilon_C:" + epsilonC);
			out.write(" sigma_C:" + sigmaC);
			out.write(" epsilon_Cl:" + epsilonCl);
			out.write(" sigma_Cl:" + sigmaCl);
			out.write("\n\n");
			out.close();

			// Fire listener that one evolution cycle is complete.
			// ---------------------------------------------------
			feedback.completeFrame(evolutionIndex);
			evolutionIndex++;
			// Check if evolution is finished.
			// -------------------------------
			if (evolver.isEvolutionFinished(evolutionIndex)) {
				evolver.onFinished();
				break;
			}
		} while (true);

		m_gridConfig.getClientFeedback().endWork();
	}

	public void start() {
		try {
			m_gridConfig.validate();
		} catch (Exception ex) {
			throw new RuntimeException(ex);
		}
		super.start();
	}

	public Configuration getConfiguration() {
		return m_gridConfig.getConfiguration();
	}

	/**
	 * Starts a client (first parameter: name of specific setup class).
	 * 
	 * @param args
	 *          String[]
	 * 
	 * @author Klaus Meffert
	 * @since 3.01
	 */
	public static void main(String[] args) {
		if (args.length < 1) {
			System.out.println("Please provide a name of the grid configuration class to use");
			System.out.println("An example class would be "
					+ "examples.grid.fitnessDistributed.GridConfiguration");
			System.exit(1);
		}
		try {
			// Setup logging.
			// --------------
			MainCmd.setUpLog4J("client", true);
			// Command line options.
			// ---------------------
			GridNodeClientConfig config = new GridNodeClientConfig();
			Options options = new Options();
			CommandLine cmd = MainCmd.parseCommonOptions(options, config, args);
			// Setup and start client.
			// -----------------------
			new DiceClient(config, args[0]);
		} catch (Exception ex) {
			ex.printStackTrace();
			System.exit(1);
		}
	}
}
